﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Hashtable=System.Collections.Hashtable;

namespace nTyrant
{
    /// <summary>
    /// Connection pool per endpoint 
    /// </summary>
    class ConnectionPool
    {
        /// <summary>
        /// Initial number of connections to create. Make this tuneable...
        /// </summary>
        private static int InitialConnections = 8;
        /// <summary>
        /// EndPoint for the pool
        /// </summary>
        public IPEndPoint EndPoint { get; private set; }
        /// <summary>
        /// Create a new pool
        /// </summary>
        /// <param name="endpoint">Endpoint for the pool</param>
        private ConnectionPool(IPEndPoint endpoint)
        {
            this.EndPoint = endpoint;

            for (int i = 0; i < InitialConnections; i++)
            {
                Connections.Enqueue(new Connection(endpoint));
            }
        }
        /// <summary>
        /// Internal lookup of endpoints to pools
        /// </summary>
        private static Hashtable lookup = new Hashtable();
        /// <summary>
        /// Method is used to get a connection from the pool.
        /// </summary>
        /// <param name="endpoint">endpoint for the connection</param>
        /// <returns>Connection to the database</returns>
        internal static Connection GetConnection(IPEndPoint endpoint)
        {
            ConnectionPool pool = GetConnectionPool(endpoint);

            return pool.GetConnection();
        }
        /// <summary>
        /// Method is used to return a connectionpool for the specified endpoint. If the pool
        /// does not exist it will be initialized.
        /// </summary>
        /// <param name="endpoint">endpoint for the pool</param>
        /// <returns>Connection pool </returns>
        private static ConnectionPool GetConnectionPool(IPEndPoint endpoint)
        {
            ConnectionPool pool = lookup.ContainsKey(endpoint) ? (ConnectionPool)lookup[endpoint] : null;

            if (null == pool)
            {
                lock (lookup)
                {
                    if (lookup.ContainsKey(endpoint))
                        return (ConnectionPool)lookup[endpoint];

                    pool = new ConnectionPool(endpoint);
                    lookup.Add(endpoint, pool);
                }
            }

            return pool;
        }
        /// <summary>
        /// Method is used to return a connection. If one doesn't exist create one. 
        /// </summary>
        /// <returns>Connection from the pool.</returns>
        private Connection GetConnection()
        {
            lock (Connections)
            {
                if (Connections.Count > 0)
                    return Connections.Dequeue();
            }

            return new Connection(this.EndPoint);
        }
        /// <summary>
        /// Fifo of connections.
        /// </summary>
        private Queue<Connection> Connections = new Queue<Connection>();
        /// <summary>
        /// Method is used to return a connection to the pool.
        /// </summary>
        /// <param name="conn">Connection to return</param>
        private void ReturnConnection(Connection conn)
        {
            if (!conn.Socket.Connected||conn.Error)
                return;

            lock (Connections)
            {
                Connections.Enqueue(conn);
            }
        }
        /// <summary>
        /// Method is used to return a connection to the pool
        /// </summary>
        /// <param name="connection">Connection to return</param>
        internal static void ReturnToPool(Connection connection)
        {
            ConnectionPool pool = GetConnectionPool(connection.EndPoint);
            pool.ReturnConnection(connection);
        }
    }

    /// <summary>
    /// Connection to the database.
    /// </summary>
    class Connection:IDisposable
    {
        /// <summary>
        /// Creates a new connection.
        /// </summary>
        /// <param name="endpoint">endpoint for the connection</param>
        internal Connection(IPEndPoint endpoint)
        {
            this.EndPoint = endpoint;
            this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.Socket.NoDelay = true;
            this.Socket.Connect(this.EndPoint);
        }
        /// <summary>
        /// Endpoint for the connection
        /// </summary>
        public IPEndPoint EndPoint { get; private set; }
        /// <summary>
        /// Flag to determine if an error was raised on the connection. This
        /// is to help weed bad connections from the pool.
        /// </summary>
        public bool Error { get; private set; }
        /// <summary>
        /// Socket holding connection to the database.
        /// </summary>
        public Socket Socket { get; private set; }
        /// <summary>
        /// Method is used to set an error on the connection. To help weed items from the pool.
        /// </summary>
        public void SetError()
        {
            this.Error = true;
        }
        /// <summary>
        /// Cleanup of connections. 
        /// </summary>
        public void Dispose()
        {
            if (!this.Error)
            {
                ConnectionPool.ReturnToPool(this);
            }
            else
            {
                try
                {
                    if (Socket.Connected)
                    {
                        Socket.Disconnect(false);
                    }
                }
                catch (Exception)
                {
                    //Do something with this exception
                }
            }
        }
    }
}
